﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;

using SBPweb.Data.Generic;

namespace SBPweb.Data
{
    /// <summary>
    /// A generic repository base class that uses various
    /// factory classes to get and retrieve specific domain
    /// objects.
    /// </summary>
    public class Repository<TDomainObject>: IRepository<TDomainObject>
    {
        
		/// <summary>
		/// string databaseConnectionString
		/// </summary>
		protected string databaseConnectionString;
		/// <summary>
		/// Database db
		/// </summary>
        protected Database db;
		/// <summary>
		/// DbTransaction transaction
		/// </summary>
		protected DbTransaction transaction;

		/// <summary>
		/// Initializes a new Repository instance
		/// </summary>
		public Repository()
		{
		}

		/// <summary>
		/// Initializes a new Repository instance
		/// </summary>
		/// <param name="databaseConnectionString">connection string to database</param>
		public Repository(string databaseConnectionString)
        {
			this.databaseConnectionString = databaseConnectionString;
			this.db = DatabaseFactory.CreateDatabase(databaseConnectionString);
        }

        /// <summary>
        /// Find all objects that match the given criteria.
        /// </summary>
        /// <typeparam name="TIdentity">Type of object used to identify
        /// the objects to find.</typeparam>
        /// <param name="selectionFactory">Factory object that can turn the
        /// identity object into the appropriate DbCommand.</param>
        /// <param name="domainObjectFactory">Factory object that can turn the
        /// returned result set into a domain object.</param>
        /// <param name="identity">Object that identifies which items to get.</param>
        /// <returns></returns>
        public List<TDomainObject> Find<TIdentity>(ISelectionFactory<TIdentity> selectionFactory, IDomainObjectFactory<TDomainObject> domainObjectFactory, TIdentity identity)
        {
            List<TDomainObject> results = new List<TDomainObject>();

            using (DbCommand command = selectionFactory.ConstructSelectCommand(db, identity))
            {
                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();

                using (IDataReader rdr = db.ExecuteReader(command))
                {
                    while (rdr.Read())
                    {
                        results.Add(domainObjectFactory.Construct(rdr));
                    }
                }

                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");
            }

            return results;
        }

        /// <summary>
        /// Find the first / only object that matches the given criteria.
        /// </summary>
        /// <typeparam name="TIdentity">Type of object used to identity the domain object desired.</typeparam>
        /// <param name="selectionFactory">Factory object that can turn
        /// the identity object into the appropriate DbCommand.</param>
        /// <param name="domainObjectFactory">Factory object that can turn the
        /// returned result set into a domain object.</param>
        /// <param name="identity">Object that identifies which item to get.</param>
        /// <returns>The domain object requested, or null if not found.</returns>
        public TDomainObject FindOne<TIdentity>(ISelectionFactory<TIdentity> selectionFactory, IDomainObjectFactory<TDomainObject> domainObjectFactory, TIdentity identity)
        {
            TDomainObject result = default(TDomainObject);
            using (DbCommand command = selectionFactory.ConstructSelectCommand(db, identity))
            {
                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();

                using (IDataReader rdr = db.ExecuteReader(command))
                {
                    if(rdr.Read())
                    {
                        result = domainObjectFactory.Construct(rdr);
                    }
                }

                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");

            }
            return result;
        }

        /// <summary>
        /// Insert the given object into the database.
        /// </summary>
        /// <param name="insertFactory">Factory used to create the command.</param>
        /// <param name="domainObj">Domain object to insert</param>
        public void Add(IInsertFactory<TDomainObject> insertFactory, TDomainObject domainObj )
        {
            using(DbCommand command = insertFactory.ConstructInsertCommand(db, domainObj))
            {

                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();

				if (transaction == null)
				{
					db.ExecuteNonQuery(command);
				}
				else
				{
					db.ExecuteNonQuery(command, transaction);
				}
				insertFactory.SetNewID(db, command, domainObj);

                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");
            }
        }

		/// <summary>
		/// Removes data from database identified by the given object 
		/// </summary>
		/// <typeparam name="TIdentityObject">Object type that identifies the data</typeparam>
		/// <param name="deleteFactory">Factory used to create the command</param>
		/// <param name="identityObj">Object that identifies the data</param>
        public void Remove<TIdentityObject>(IDeleteFactory<TIdentityObject> deleteFactory, TIdentityObject identityObj )
        {
            using(DbCommand command = deleteFactory.ConstructDeleteCommand(db, identityObj))
            {
                
                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();
                
                if (transaction == null)
				{
					db.ExecuteNonQuery(command);
				}
				else
				{
					db.ExecuteNonQuery(command, transaction);
				}

                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");

			}
        }

		/// <summary>
		/// Updates the given object in the database.
		/// </summary>
		/// <param name="updateFactory">Factory used to create the command</param>
		/// <param name="domainObj">Domain object to update</param>
        public void Update(IUpdateFactory<TDomainObject> updateFactory, TDomainObject domainObj)
        {
            using(DbCommand command = updateFactory.ConstructUpdateCommand(db, domainObj))
            {

                SBPweb.Common.Diagnostics.MeasuredInterval mi = new SBPweb.Common.Diagnostics.MeasuredInterval();
                
                if (transaction == null)
				{
					db.ExecuteNonQuery(command);
				}
				else
				{
					db.ExecuteNonQuery(command, transaction);
				}

                LogManager.WriteVerbose(this, string.Format("Execution of {0} [{1}] took {2}.", command, command.CommandText, mi.Stop()), "SBPweb.Data.Repository");
            
            }
        }

		/// <summary>
		/// Start transaction
		/// </summary>
		public virtual void BeginTransaction()
		{
			DbConnection connection = db.OpenConnection();
			transaction = connection.BeginTransaction();
		}

		/// <summary>
		/// Commit transaction
		/// </summary>
		/// <param name="domainObj"></param>
		public virtual void Commit(TDomainObject domainObj)
		{
			transaction.Commit();
		}

		/// <summary>
		/// Rollback transaction
		/// </summary>
		/// <param name="domainObj"></param>
		public virtual void Rollback(TDomainObject domainObj)
		{
			transaction.Rollback();
		}

		/// <summary>
		/// Returns domain object by identity
		/// </summary>
		/// <param name="identityObj"></param>
		/// <returns></returns>
		protected virtual TDomainObject GetDomainObjectByIdentity(object identityObj)
		{
			throw new System.NotImplementedException("For transactions with Remove you have to re-implement GetDomainObjectByIdentity method.");
		}
    }
}
